package oen.game.csv.loader;

import im.oen.boot.common.utils.Checker;
import im.oen.boot.common.utils.ReflectUtil;
import im.oen.boot.common.utils.Runner;
import oen.game.csv.loader.annotation.CSVField;
import oen.game.csv.loader.data.FieldData;
import oen.game.csv.loader.util.FieldValueConvert;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;

import javax.naming.NoPermissionException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.*;
import java.util.logging.Logger;

/**
 * <p>描述：CSV文件加载器</p>
 *
 * <p>创建时间：2021-11-09 12:35</p>
 * <p>更新时间：暂无</p>
 *
 * @author Kevin.Xu
 * @version 1.0
 */
public class CSVLoader {
    private static final Logger log = Logger.getLogger(CSVLoader.class.getName());

    public static <T> List<T> read(String path, Class<T> clazz) throws Exception {
        File file = new File(path);
        if (!file.exists()) {
            throw new FileNotFoundException(path);
        }
        if (!file.canRead()) {
            throw new NoPermissionException();
        }

        return read(new FileInputStream(file), clazz);
    }

    public static <T> List<T> read(InputStream inputStream, Class<T> clazz) throws Exception {
        if (inputStream == null) {
            throw new FileNotFoundException();
        }

        Iterator<CSVRecord> record = CSVFormat.EXCEL.parse(new InputStreamReader(inputStream)).stream().iterator();
        if (!record.hasNext()) {
            throw new NullPointerException("CSV can not be empty");
        }

        List<Field> fields = ReflectUtil.fetchFields(clazz);
        Map<Field, FieldData> heads = CSVLoader.readHead(fields, record.next());
        List<T> list = new ArrayList();
        while (record.hasNext()) {
            final CSVRecord data = record.next();
            boolean emptyObject = true;
            T obj = ReflectUtil.newObj(clazz);
            for (Field field : fields) {
                final FieldData fieldData = heads.get(field);
                if (fieldData == null) {
                    continue;
                }

                String dataValue = fieldData.getDefaultValue();
                if (fieldData.getIdx() >= 0) {
                    dataValue = Runner.safeRun(() -> {
                        String v = data.get(fieldData.getIdx());
                        return Checker.isEmpty(v) ? null : v;
                    }, dataValue);
                }
                Object v = FieldValueConvert.to(dataValue, fieldData);
                if (v != null) {
                    ReflectUtil.setFieldValue(obj, field, v);
                    emptyObject = false;
                }
            }

            if (!emptyObject) {
                list.add(obj);
            }
        }
        return list;
    }

    private static FieldData getRecordIndex(Field field, Map<String, Integer> heads) {
        FieldData data = new FieldData(field);
        data.setDeclaringType(field.getGenericType());
        CSVField csvField = field.getAnnotation(CSVField.class);
        if (csvField == null) {
            String name = field.getName();
            Integer idx = heads.get(name);
            data.setIdx((idx == null) ? -1 : idx);
            return data;
        }
        data.setId(csvField.id());
        data.setParentId(csvField.parentId());
        data.setGroupId(csvField.groupId());
        data.setDefaultValue(csvField.dft());
        data.setSplitSymbol(csvField.split());
        int index = csvField.idx();
        if (index >= 0) {
            data.setIdx(heads.values().contains(index) ? index : -1);
            return data;
        }

        String cfv = csvField.value();
        if (Checker.isEmpty(cfv)) {
            cfv = field.getName();
        }

        Integer idx = heads.get(cfv);
        data.setIdx((idx == null) ? -1 : idx);
        return data;
    }

    static Map<Field, FieldData> readHead(List<Field> fields, CSVRecord record) {
        Map<String, Integer> heads = new HashMap<>();

        if (record != null) {
            int size = record.size();
            for (int i = 0; i < size; i++) {
                String field = record.get(i);
                heads.put(Checker.isEmpty(field) ? "" : field, i);
            }
        }

        Map<Field, FieldData> fieldDataMap = new HashMap<>();
        for (Field field : fields) {
            FieldData fieldData = getRecordIndex(field, heads);
            fieldDataMap.put(field, fieldData);
        }
        return fieldDataMap;
    }
}
